package top.wshape1.rpc.common.extension;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Wshape1
 * @version 1.0
 * @description 自定义SPI加载器，加载扩展类
 * @since 2024-02-08
 */

public class ExtensionLoader<T> {

    private static final String[] EXTENSION_PATH = {
            "META-INF/jjrpc/",
            "META-INF/services/"
    };

    private static final Map<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    private final Class<T> interfaceClass;

    private final Map<String, Class<T>> extensionClasses = new ConcurrentHashMap<>();

    private final Map<String, T> extensionInstances = new ConcurrentHashMap<>();

    @SuppressWarnings("unchecked")
    public static <E> ExtensionLoader<E> getExtensionLoader(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        ExtensionLoader<?> loader = EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<>(type));
            loader = EXTENSION_LOADERS.get(type);
        }
        return (ExtensionLoader<E>) loader;
    }

    @SuppressWarnings("unchecked")
    private ExtensionLoader(Class<T> clazz) {
        this.interfaceClass = clazz;
        ClassLoader classLoader = ExtensionLoader.class.getClassLoader();

        String className = clazz.getName();
        for (String path : EXTENSION_PATH) {
            String filename = path + className;
            try {
                Enumeration<URL> urls = classLoader.getResources(filename);
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();

                    try (InputStream is = url.openStream();
                         BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))
                    ) {
                        for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                            // skip comments
                            int ci = line.indexOf("#");
                            if (ci >= 0) {
                                line = line.substring(0, ci);
                            }
                            line = line.trim();
                            if (line.isEmpty()) {
                                continue;
                            }

                            String[] split = line.split("=");

                            try {
                                extensionClasses.putIfAbsent(split[0].trim(), (Class<T>) Class.forName(split[1].trim()));
                            } catch (ClassNotFoundException ignored) {
                                // skip
                            }
                        }
                    }

                }
            } catch (IOException ignored) {
                // skip
            }
        }
    }


    public T getExtension(String key) {
        T t = extensionInstances.get(key);
        if (t == null) {
            Class<T> tClass = extensionClasses.get(key);
            if (tClass == null) {
                throw new RuntimeException("No such extension " + key + " for " + interfaceClass.getName());
            } else {
                try {
                    extensionInstances.putIfAbsent(key, tClass.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    throw new RuntimeException("Extension " + key + " for " + interfaceClass.getName() + " can not be instantiated.", e);
                }
                t = extensionInstances.get(key);
            }
        }
        return t;
    }

    public Class<T> getExtensionClass(String key) {
        return extensionClasses.get(key);
    }

    public void clearExtensionInstances() {
        extensionInstances.clear();
    }
}
